/**
 *  WaveDevice.cpp
 *
 *  Copyright (C) 2008  David Andrs <pda@jasnapaka.com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "StdAfx.h"
#include "mLasq.h"
#include <mmsystem.h>
#include "WaveDevice.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

const int CWaveDevice::BLOCK_SIZE = 8192;
const int CWaveDevice::BLOCK_COUNT = 8;

static void CALLBACK waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2) {
	// pointer to free block counter
	CWaveDevice *dev = (CWaveDevice *) dwInstance;
	// ignore calls that occur due to opening and closing the device.
	if (uMsg != WOM_DONE)
		return;
	EnterCriticalSection(&dev->CSWave);
	dev->WaveFreeBlockCount++;
	LeaveCriticalSection(&dev->CSWave);
	SetEvent(dev->HFreeEvent);
}

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CWaveDevice::CWaveDevice() {
	// playback
	InitializeCriticalSection(&CSWave);
	HFreeEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

	unsigned char *buffer;
	DWORD totalBufferSize = (BLOCK_SIZE + sizeof(WAVEHDR)) * BLOCK_COUNT;
	// allocate memory for the entire set in one go
	if ((buffer = (unsigned char *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, totalBufferSize)) == NULL) {
//		fprintf(stderr, "Memory allocation error\n");
//		ExitProcess(1);
	}

	// and set up the pointers to each bit
	WaveBlocks = (WAVEHDR *) buffer;
	buffer += sizeof(WAVEHDR) * BLOCK_COUNT;

	for (int i = 0; i < BLOCK_COUNT; i++) {
		WaveBlocks[i].dwBufferLength = BLOCK_SIZE;
		WaveBlocks[i].lpData = (char *) buffer;
		buffer += BLOCK_SIZE;
	}

	WaveFreeBlockCount = BLOCK_COUNT;
	WaveCurrentBlock = 0;
}

CWaveDevice::~CWaveDevice() {
	//
    HeapFree(GetProcessHeap(), 0, WaveBlocks);
	CloseHandle(HFreeEvent);
	DeleteCriticalSection(&CSWave);
}

void CWaveDevice::Open() {
	// set up the WAVEFORMATEX structure.
	WFX.nSamplesPerSec = 44100;
	WFX.wBitsPerSample = 16;
	WFX.nChannels = 2;
	WFX.cbSize = 0;
	WFX.wFormatTag = WAVE_FORMAT_PCM;
	WFX.nBlockAlign = (WFX.wBitsPerSample * WFX.nChannels) >> 3;
	WFX.nAvgBytesPerSec = WFX.nBlockAlign * WFX.nSamplesPerSec;

	if (waveOutOpen(&HWaveOut, WAVE_MAPPER, &WFX, (DWORD_PTR) waveOutProc, (DWORD_PTR) this, CALLBACK_FUNCTION) != MMSYSERR_NOERROR) {
//		fprintf(stderr, "%s: unable to open wave mapper device\n", argv[0]);
	}
}

void CWaveDevice::Reset() {
	waveOutReset(HWaveOut);

	WaveFreeBlockCount = BLOCK_COUNT;
	WaveCurrentBlock = 0;
}

void CWaveDevice::Pause() {
    waveOutPause(HWaveOut);
}

void CWaveDevice::Close() {
    waveOutClose(HWaveOut);
}

void CWaveDevice::WaitForFinish() {
	// wait for all blocks to complete
	while (WaveFreeBlockCount < BLOCK_COUNT)
		Sleep(10);
	// unprepare any blocks that are still prepared
	for (int i = 0; i < WaveFreeBlockCount; i++)
		if (WaveBlocks[i].dwFlags & WHDR_PREPARED)
			waveOutUnprepareHeader(HWaveOut, &WaveBlocks[i], sizeof(WAVEHDR));
}

void CWaveDevice::WriteAudio(LPSTR data, int size) {
	int remain;

	WAVEHDR *current = &WaveBlocks[WaveCurrentBlock];
	while (size > 0) {
		// first make sure the header we're going to use is unprepared
		if (current->dwFlags & WHDR_PREPARED)
			waveOutUnprepareHeader(HWaveOut, current, sizeof(WAVEHDR));

		if (size < (int) (BLOCK_SIZE - current->dwUser)) {
			memcpy(current->lpData + current->dwUser, data, size);
			current->dwUser += size;
			break;
		}

		remain = BLOCK_SIZE - current->dwUser;
		memcpy(current->lpData + current->dwUser, data, remain);
		size -= remain;
		data += remain;
		current->dwBufferLength = BLOCK_SIZE;
		waveOutPrepareHeader(HWaveOut, current, sizeof(WAVEHDR));
		waveOutWrite(HWaveOut, current, sizeof(WAVEHDR));

		EnterCriticalSection(&CSWave);
		WaveFreeBlockCount--;
		LeaveCriticalSection(&CSWave);

		// wait for a block to become free
/*		while (!WaveFreeBlockCount)
			Sleep(10);
*/
		if (!WaveFreeBlockCount)
			WaitForSingleObject(HFreeEvent, INFINITE);
		ResetEvent(HFreeEvent);

		// point to the next block
		WaveCurrentBlock = (WaveCurrentBlock + 1) % BLOCK_COUNT;
		current = &WaveBlocks[WaveCurrentBlock];
		current->dwUser = 0;
	}
}

BOOL CWaveDevice::SetVolume(DWORD volume) {
	return waveOutSetVolume(HWaveOut, volume) == MMSYSERR_NOERROR;
}

DWORD CWaveDevice::GetVolume() {
	DWORD volume = 0;
	waveOutGetVolume(HWaveOut, &volume);
	return volume;
}
